package com.fm.parser.json;

import com.fm.base.annotation.UnThreadSafe;
import com.fm.base.json.*;
import com.fm.base.json.exception.JsonTypeException;

import javax.json.stream.JsonParser;
import java.io.StringReader;
import java.util.Stack;
import java.util.function.Supplier;

/**
 * @author finalmachine
 * @param <O> JsonObject Impl
 * @param <A> JsonArray Impl
 */
@UnThreadSafe
public class BasicJsonReader<O extends JsonObject, A extends JsonArray> implements JsonReader<O, A> {
    private JsonParser parser;
    private Supplier<O> supplier1;
    private Supplier<A> supplier2;
    private Stack<JsonStructure> stack;
    private Stack<String> keys;

    public BasicJsonReader(String jsonString, Supplier<O> supplier1, Supplier<A> supplier2) {
        parser = new BasicJsonParser(new StringReader(jsonString));
        this.supplier1 = supplier1;
        this.supplier2 = supplier2;
        stack = new Stack<>();
        keys = new Stack<>();
    }

    @Override
    public JsonStructure read() {
        boolean inArray = false;
        while (parser.hasNext()) {
            JsonParser.Event event = parser.next();
            switch (event) {
                case START_OBJECT:
                    inArray = false;
                    stack.push(supplier1.get());
                    break;
                case START_ARRAY:
                    inArray = true;
                    stack.push(supplier2.get());
                    break;
                case KEY_NAME:
                    keys.push(parser.getString());
                    break;
                case VALUE_STRING:
                    if (inArray) {
                        ((JsonArray) stack.peek()).add(parser.getString());
                    } else {
                        ((JsonObject) stack.peek()).append(keys.pop(), parser.getString());
                    }
                    break;
                case VALUE_NUMBER:
                    if (inArray) {
                        ((JsonArray) stack.peek()).add(parser.getBigDecimal());
                    } else {
                        ((JsonObject) stack.peek()).append(keys.pop(), parser.getBigDecimal());
                    }
                    break;
                case VALUE_NULL:
                    if (inArray) {
                        ((JsonArray) stack.peek()).add((JsonValue) null);
                    } else {
                        ((JsonObject) stack.peek()).put(keys.pop(), (JsonValue) null);
                    }
                    break;
                case VALUE_TRUE:
                    if (inArray) {
                        ((JsonArray) stack.peek()).add(true);
                    } else {
                        ((JsonObject) stack.peek()).append(keys.pop(), true);
                    }
                    break;
                case VALUE_FALSE:
                    if (inArray) {
                        ((JsonArray) stack.peek()).add(false);
                    } else {
                        ((JsonObject) stack.peek()).append(keys.pop(), false);
                    }
                    break;
                case END_OBJECT:
                    JsonObject jsonObject = (JsonObject) stack.pop();
                    if (stack.isEmpty()) {
                        return jsonObject;
                    } else if (stack.peek() instanceof JsonObject) {
                        ((JsonObject) stack.peek()).put(keys.pop(), jsonObject);
                    } else if (stack.peek() instanceof JsonArray) {
                        ((JsonArray) stack.peek()).add(jsonObject);
                        inArray = true;
                    } else {
                        throw new JsonTypeException("an useless JsonObject in the string");
                    }
                    break;
                case END_ARRAY:
                    JsonArray jsonArray = (JsonArray) stack.pop();
                    if (stack.isEmpty()) {
                        return jsonArray;
                    } else if (stack.peek() instanceof JsonObject) {
                        ((JsonObject) stack.peek()).put(keys.pop(), jsonArray);
                    } else if (stack.peek() instanceof JsonArray) {
                        ((JsonArray) stack.peek()).add(jsonArray);
                    } else {
                        throw new JsonTypeException("an useless JsonArray in the string");
                    }
                    inArray = false;
                    break;
                default:
                    throw new IllegalArgumentException("not support event " + event);
            }
        }
        parser.close();
        return null;
    }

    @Override
    @SuppressWarnings("unchecked")
    public O readJsonObject() {
        return (O) read();
    }

    @Override
    @SuppressWarnings("unchecked")
    public A readJsonArray() {
        return (A) read();
    }
}
